package com.lhz.framework.security.interceptor;

import cn.hutool.core.lang.Assert;
import com.lhz.common.constant.LimitLockType;
import com.lhz.common.constant.ResponseState;
import com.lhz.common.utils.ip.IpUtils;
import com.lhz.framework.exception.ServiceException;
import com.lhz.framework.security.annotation.RateLimiter;
import com.lhz.framework.security.limiter.RedisLuaConfig;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * Token身份验证
 */
@Component
@Slf4j
public class RateLimiterHandlerInterceptor implements HandlerInterceptor {

    @Resource
    private RedisLuaConfig redisLuaConfig;

    private static final int DEFAULT_PERIOD = 1;
    private static final int DEFAULT_COUNT = 10;

    private static final String LIMITER_KEY = "limiter:";
    private static final String LIMITER_IP_KEY = "limiter-ip:";
    private static final String LIMITER_TOKEN_KEY = "limiter-token:";
    private static final String LIMITER_ENTITY_PARAM_KEY = "limiter-entity-param:";

    private static final String LIMITER_METHOD_PARAM_KEY = "limiter-method-param:";

    private static final String SUCCESS_CODE = "1";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        Method method = ((HandlerMethod) handler).getMethod();

        String path = request.getServletPath();
        String httpMethod = request.getMethod(); //GET、PUT、DELETE、POST

        //获取当前请求IP
        String ip = IpUtils.getIpAddr(request);

        RateLimiter rateLimiter = method.getAnnotation(RateLimiter.class);
        if (rateLimiter == null) {
            return true;
        }
        int period = rateLimiter.period();
        int count = rateLimiter.count();
        LimitLockType limitLockType = rateLimiter.lockType();
        // 默认限制
        String key = LIMITER_KEY + httpMethod + "@" + path;

        if (limitLockType.equals(LimitLockType.IP)) {
            //对ip进行次数限制s
            key = LIMITER_IP_KEY + ip + "@" + httpMethod + "@" + path;
        }

        if (limitLockType.equals(LimitLockType.TOKEN)) {
            // 从header中获取
            String token = request.getHeader("token");
            key = LIMITER_TOKEN_KEY + token + "@" + httpMethod + "@" + path;
        }

        // 从请求参数中获取
        Object[] args = method.getParameters();
        if (args.length != 0) {
            if (limitLockType.equals(LimitLockType.ENTITY_PARAM)) {
                Object paramNameValue = null;
                Object arg = args[0];
                String paramName = rateLimiter.paramName();
                Assert.notNull(paramName, "限流参数【paramName】索引值不存在");
                Class<?> aClass = arg.getClass();
                Field[] declaredFields = aClass.getDeclaredFields();
                for (Field field : declaredFields) {
                    if (paramName.equals(field.getName())) {
                        field.setAccessible(true);
                        paramNameValue = field.get(arg);
                        break;
                    }
                }
                Assert.notNull(paramNameValue, "参数：【" + paramName + "】不存在");
                key = LIMITER_ENTITY_PARAM_KEY + paramNameValue + "@" + httpMethod + "@" + path;
            }

            if (limitLockType.equals(LimitLockType.METHOD_PARAM)) {
                int keyNum = rateLimiter.keyNum();
                Assert.isFalse(keyNum < 0 || keyNum > args.length - 1, "加锁参数【keyNum】索引值不存在");
                Object methodParam = args[keyNum];
                Assert.notNull(methodParam, "限流参数索引【keyNum】不存在");
                key = LIMITER_TOKEN_KEY + methodParam + "@" + httpMethod + "@" + path;
            }
        }
        String res;
        try {
            List<String> keyList = new ArrayList<>();
            keyList.add(key);
            //表示时间周期内运行访问得次数
            keyList.add(String.valueOf(count)); //count
            keyList.add(String.valueOf(period));//period
            res = redisLuaConfig.runLuaScript(keyList);
        } catch (Exception e) {
            log.error(ResponseState.SERVER_LIMITER_ERROR.getName());
            throw new ServiceException(ResponseState.SERVER_LIMITER_ERROR);
        }
        //正常执行RedisLua
        if (!SUCCESS_CODE.equals(res)) {
            log.error(ResponseState.SERVER_LIMITER.getName());
            throw new ServiceException(ResponseState.SERVER_LIMITER);
        }

        return true;
    }
}
